#ifndef __C_MM_PAGE_H__
#define __C_MM_PAGE_H__

#include "std/list.h"
#include "arch/pgd.h"
#include "kernel/spinlock.h"

#ifdef __cplusplus
extern "C" {
#endif

// lock a page, causing the page neither
// discardable nor swapable
#define PAGE_FLAG_LOCKED		(1)

// determine if the page is swapable
// a page maked as "not swappable" means
// the page must be locked in memory
#define PAGE_FLAG_SWAPABLE		(2)

// indicate if the page is still in memory
// or it is already swapped onto disk
#define PAGE_FLAG_SWAPPED		(4)

// the page is discardable, means the page
// could be discard without any further
// notification
#define PAGE_FLAG_DISCARDABLE	(8)

// the page is reserved and it shall never
// be accessed or used
#define PAGE_FLAG_RESERVED		(16)

// indicate the page is used as a page_dir
// or page_table_entry
#define PAGE_FLAG_PGDTE			(32)

#define IS_PAGE_DISCARDABLE(p)	((p)->flags & PAGE_FLAG_DISCARDABLE	\
	&& (!((p)->flags & PAGE_FLAG_LOCKED)))

#define IS_PAGE_SWAPABLE(p)	(p)->flags & PAGE_FLAG_SWAPABLE	\
	&& (!((p)->flags & PAGE_FLAG_LOCKED)))

#define DEFAULT_PAGE_FLAG	(PAGE_FLAG_SWAPABLE)

typedef struct page
{
	// the high 20 bits of flag stores virtual address
	// the low 12 bits stores flags
	unsigned int flags;
	unsigned int lru_count;
	listnode_t ownerlist;
}
page_t;

#define lock_page(p)			do { (p)->flags |= PAGE_FLAG_LOCKED; } while (0)
#define unlock_page(p)			do { (p)->flags &= ~PAGE_FLAG_LOCKED; } while (0)
#define virt_addr(p)			(((p)->flags) & 0xFFFFF000)
#define set_page_virt_addr(p, v)		\
	do {	\
		(p)->flags &= (PAGE_SZ - 1);	\
		(p)->flags |= (v) & ~(PAGE_SZ - 1);	\
	} while (0)

#define GLOBAL_PAGE_DATA_FLAG_SWAP_ENABLED		(1)

typedef struct global_page_data
{
	unsigned int flags;
	page_t *page_table;
	spinlock_t spinlock;
	unsigned int total_pages;
	
	uint hi_area_free_page_count;
	uint low_area_free_page_count;
	uint reserved_page_count;

	// lists
	listnode_t hi_area_free_page_list;
	listnode_t low_area_free_page_list;
	listnode_t reserved_page_list;
	listnode_t dirty_page_list;
}
global_page_data_t;

extern global_page_data_t global_page_data;

// the start pointer to the global page
// table, shall be initialized once
#define global_page_table			(global_page_data.page_table)
#define global_page_table_start		(global_page_data.page_table)

// this indicate how many pages in total
#define global_total_pages			(global_page_data.total_pages)

// this indicates the free page list
#define global_hi_area_free_page_list		(global_page_data.hi_area_free_page_list)
#define global_low_area_free_page_list		(global_page_data.low_area_free_page_list)

// this indicates the reserved page list
#define global_reserved_page_list	(global_page_data.reserved_page_list)

// this indicates the dirty page list
#define global_dirty_page_list		(global_page_data.dirty_page_list)

// page count
#define global_hi_area_free_page_count		(global_page_data.hi_area_free_page_count)
#define global_low_area_free_page_count		(global_page_data.low_area_free_page_count)
#define global_reserved_page_count			(global_page_data.reserved_page_count)
#define global_total_free_page_count		(global_hi_area_free_page_count + global_low_area_free_page_count)
#define global_avail_page_count				(global_total_pages - global_reserved_page_count)

#define VALID_PAGE(p)	((p) >= global_page_table_start &&	\
	(p) < (global_page_table_start + global_total_pages))

#define PAGE_IDX_VALID(idx)		((idx) < global_total_pages)

#define PAGE_IDX(p)		((p) - global_page_table_start)
#define GET_PAGE_IDX(p)	PAGE_IDX(p)
#define IDX2PAGE(idx)	(global_page_table_start + (idx))
#define PAGE_PHYADDR(p)	(PAGE_IDX(p) * PAGE_SZ)

// page allocation strategy
#define __GFP_LOW				(1)
#define __GFP_LOWONLY			(2)
#define __GFP_DMA				(4)
#define __GFP_HIGHONLY			(8)			// you have risk of using this if your memory is less than 16MB
#define __GFP_HIGH				(16)
#define __GFP_WAIT				(32)
#define __GFP_LOCK				(64)		// allocate the page with locked status
#define __GFP_END__				(64)		// note: need change if any new gfp flag added
#define __GFP_MASK				(__GFP_END__ * 2 - 1)

#define GFP_BUFFER				(__GFP_HIGH | __GFP_WAIT)

// global initializor
void global_init_page_module(void);

// allocate one page
page_t* alloc_page(uint gfpmsk);

// free a physical page
void free_page(page_t *p);

// get the page of a mapped virtual address
// the page is marked lock of lock = true
page_t* virt_addr_page(uint virtaddr, bool lock);

#ifdef __cplusplus
}
#endif
#endif
/* EOF */
